package org.cedar.syntaxcore;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.cedar.exceptions.CalcException;
import org.cedar.exceptions.VariantIOException;

/**
 * <p>Title: 表达式语法分析</p>
 *
 * <p>Description: 变量环境，为公式执行中的变量提供存储方便
 * 取：当变量不在环境中时，需要从数据源取回，并设置在环境中。
 * 设：设置环境中变量，同时也为数据源设置
 * </p>
 *
 * <p>Copyright:  (c) 2005</p>
 *
 * <p>Company: net.wzh</p>
 *
 * @author wzh
 * @version 1.0
 */
public class SyntaxContext implements VariantContext{
	
	private HashMap varMap = new HashMap();
	private HashMap funcMap = new HashMap();
    private VariantContext dataSource;

    //初始值均为空
    public SyntaxContext() {}

    //赋初始值
    public SyntaxContext(VariantContext ds) {
      this.dataSource = ds;
    }

    //设置数据源
    public void setDataSource(VariantContext ds){
        this.dataSource = ds;
    }

    //获取数据源
    public VariantContext getDataSource(){
        return this.dataSource;
    }
    //获取变量值
    public Object get(String variantName)throws VariantIOException{
        Object v = null;
        if (null != this.dataSource) {
            v = this.dataSource.get(variantName);
        }else
        	v = this.varMap.get(variantName);
        return v;
    }

    //设置变量值1
    public void put(String variantName, Object value)throws VariantIOException {
        if(null != this.dataSource ){
                this.dataSource.put(variantName, value);
        }else
        	this.varMap.put(variantName,value);
    }
    public void putAll(Map params)throws VariantIOException {
    	if(null != this.dataSource ){
    		for(Iterator<String> it = params.keySet().iterator();it.hasNext();){
    			String key = it.next();
    			this.dataSource.put(key,params.get(key));
    		}
    	}else
    		this.varMap.putAll(params);
    }
    //设置变量值2
    public void put(ExecutionItem item, Object value)throws VariantIOException {
        if (item.itemParams == null)
            this.put(item.itemString, value);
        else
            this.put(item.itemString, item.itemParams, value);
    }

    public boolean containVariant(String varName) {
    	if(null != this.dataSource){
          return this.dataSource.containVariant(varName);
    	}else
    		return this.varMap.containsKey(varName);
    }

    //依据条件获取变量值
    public Object get(String variantName, List params)throws VariantIOException {
        if (params == null)
            return this.get(variantName);
        Object v = null;
        if(null != this.dataSource){
            v = this.dataSource.get(variantName, params);
        }else
        	v = this.varMap.get(variantName);
        return v;
    }

    //依据条件设置变量值
    public void put(String variantName, List params, Object value)throws VariantIOException {
        if (null == params)
            this.put(variantName, value);
        else if(null != this.dataSource){
                this.dataSource.put(
                variantName, params, value);
        }else
        	this.varMap.put(variantName,value);
    }


    public MetaElement not_(MetaElement a) throws CalcException {
        switch (a.getValueType()) {
        case VariantType.vtInt:
            return new MetaElement(new Integer( -a.getAsInt()));
        case VariantType.vtDouble:
            return new MetaElement(new Double( -a.getAsDouble()));
        case VariantType.vtBool:
            return new MetaElement(new Boolean(!a.getAsBoolean()));
        default:
            throw new CalcException(4001);
        }
    }

    public MetaElement plus_(MetaElement a) {
        return new MetaElement(a);
    }

    public MetaElement plus_(MetaElement b, MetaElement a) throws
            CalcException {
        switch (a.getValueType()) {
        case VariantType.vtBool:
            switch (b.getValueType()) {
            case VariantType.vtString:
                return new MetaElement(a.toString() + b.toString());
            case VariantType.vtInt:
                return new MetaElement(new Integer((a.getAsBoolean() ? 1 : 0) +
                        b.getAsInt()));
            case VariantType.vtDouble:
                return new MetaElement(new Double((a.getAsBoolean() ? 1 : 0) +
                                                  b.getAsDouble()));
            default:
                throw new CalcException(4002);
            }
        case VariantType.vtInt:
            switch (b.getValueType()) {
            case VariantType.vtBool:
                return new MetaElement(new Integer(b.getAsBoolean() ? 1 :
                        0 + a.getAsInt()));
            case VariantType.vtInt:
                return new MetaElement(new Integer(a.getAsInt() +
                        b.getAsInt()));
            case VariantType.vtDouble:
                return new MetaElement(new Double((a.getAsInt() +
                        b.getAsDouble())));
            case VariantType.vtLong:
            	return new MetaElement(new Long((a.getAsInt() +
            			b.getAsLong())));
            case VariantType.vtString:
                return new MetaElement(a.toString() + b.toString());
            default:
                throw (new CalcException(4002));
            }
        case VariantType.vtDouble:
            switch (b.getValueType()) {
            case VariantType.vtBool:
                return new MetaElement(new Double(b.getAsBoolean() ? 1 :
                                                  0 + a.getAsDouble()));
            case VariantType.vtInt:
                return new MetaElement(new Double(a.getAsDouble() + b.getAsInt()));
            case VariantType.vtDouble:
                return new MetaElement(new Double(a.getAsDouble() +
                                                  b.getAsDouble()));
            case VariantType.vtString:
                return new MetaElement(a.toString() + b.toString());
            case VariantType.vtLong:
            	return new MetaElement(new Double((a.getAsInt() +
            			b.getAsDouble())));
            case VariantType.vtDateTime:
                throw (new CalcException(
                        4004));
            default:
                throw new CalcException(4005);
            }
        case VariantType.vtString:
            switch (b.getValueType()) {
            case VariantType.vtBool:
            case VariantType.vtInt:
            case VariantType.vtDouble:
            case VariantType.vtString:
            case VariantType.vtLong:
            case VariantType.vtDateTime:
                return new MetaElement(a.toString() + b.toString());
            default:
                throw new CalcException(4006);
            }
        default:
            throw new CalcException(4002);
        }
    }

    public MetaElement minus_(MetaElement b, MetaElement a) throws
            CalcException {
        switch (a.getValueType()) {
        case VariantType.vtInt:
            switch (b.getValueType()) {
            case VariantType.vtInt:
                return new MetaElement(new Integer(a.getAsInt() -
                        b.getAsInt()));
            case VariantType.vtDouble:
                return new MetaElement(new Double(a.getAsInt() -
                                                  b.getAsDouble()));
            case VariantType.vtLong:
            	return new MetaElement(new Long(a.getAsInt() -
            			b.getAsLong()));
            default:
                throw (new CalcException(4007));
            }
        case VariantType.vtDouble:
            switch (b.getValueType()) {
            case VariantType.vtInt:
                return new MetaElement(new Double(a.getAsDouble() -
                                                  b.getAsInt()));
            case VariantType.vtDouble:
                return new MetaElement(new Double(a.getAsDouble() -
                                                  b.getAsDouble()));
            case VariantType.vtLong:
            	return new MetaElement(new Double(a.getAsDouble() -
            			b.getAsLong()));
            default:
                throw (new CalcException(
                        4007));
            }
        default:
            throw (new CalcException(4008));
        }
    }

    public MetaElement multi_(MetaElement b, MetaElement a) throws
            CalcException {
        switch (a.getValueType()) {
        case VariantType.vtInt:
            switch (b.getValueType()) {
            case VariantType.vtInt:
                return new MetaElement(new Integer(a.getAsInt() *
                        b.getAsInt()));
            case VariantType.vtDouble:
                return new MetaElement(new Double(a.getAsInt() *
                                                  b.getAsDouble()));
            case VariantType.vtLong:
            	return new MetaElement(new Double(a.getAsInt() -
            			b.getAsLong()));
            default:
                throw (new CalcException(4009));
            }
        case VariantType.vtDouble:
            switch (b.getValueType()) {
            case VariantType.vtInt:
                return new MetaElement(new Double(a.getAsDouble() *
                                                  b.getAsInt()));
            case VariantType.vtDouble:
                return new MetaElement(new Double(a.getAsDouble() *
                                                  b.getAsDouble()));
            case VariantType.vtLong:
            	return new MetaElement(new Double(a.getAsDouble() -
            			b.getAsLong()));
            default:
                throw (new CalcException(4009));
            }
        default:
            throw (new CalcException(4010));
        }
    }

    public MetaElement mod_(MetaElement b, MetaElement a) throws CalcException {
    	boolean isLong = false;
    	switch (a.getValueType()) {
	    	case VariantType.vtLong:
	    		isLong = true;
	    	case VariantType.vtInt:
	    		switch (b.getValueType()) {
		    		case VariantType.vtInt:
		    				return isLong? new MetaElement(a.getAsLong() % b.getAsInt()) : 
		    					           new MetaElement(a.getAsInt() % b.getAsInt());
		    		case VariantType.vtLong:
		    				return new MetaElement(a.getAsLong() %
		    						b.getAsLong());
		    		default:
		    			throw (new CalcException(4011));
	    		}
	    	default:
	            throw (new CalcException(4012));
        }
    }
    public MetaElement div_(MetaElement b, MetaElement a) throws CalcException {
        switch (a.getValueType()) {
        case VariantType.vtInt:
            switch (b.getValueType()) {
            case VariantType.vtInt:
                if (((Integer) b.value()).intValue() == 0)
                    return new MetaElement(new Integer(0));
                else
                    return new MetaElement(new Integer(a.getAsInt() /
                            b.getAsInt()));
            case VariantType.vtDouble:
                if (((Double) b.value()).intValue() == 0)
                    return new MetaElement(new Double(0.0));
                else
                    return new MetaElement(new Double(a.getAsDouble() /
                            b.getAsDouble()));
            case VariantType.vtLong:
            	if (((Long) b.value()).intValue() == 0)
                    return new MetaElement(new Integer(0));
                else
                    return new MetaElement(new Long(a.getAsInt() /
                            b.getAsLong()));
            default:
                throw (new CalcException(4013));
            }
        case VariantType.vtDouble:
            switch (b.getValueType()) {
            case VariantType.vtInt:
                if (((Integer) b.value()).intValue() == 0)
                    return new MetaElement(new Double(0.0));
                else
                    return new MetaElement(new Double(a.getAsDouble() /
                            b.getAsInt()));
            case VariantType.vtDouble:
                if (((Double) b.value()).intValue() == 0)
                    return new MetaElement(new Double(0.0));
                else
                    return new MetaElement(new Double(a.getAsDouble() /
                            b.getAsDouble()));
            case VariantType.vtLong:
            	if (((Long) b.value()).intValue() == 0)
            		return new MetaElement(new Long(0));
            	else
            		return new MetaElement(new Double(a.getAsDouble() /
            				b.getAsLong()));
            default:
                throw (new CalcException(4013));
            }
        default:
            throw (new CalcException(4014));
        }
    }

    public MetaElement lg(MetaElement a, MetaElement b) throws CalcException {
    	MetaElement ret = lt(a,b);
    	ret.value = new Boolean(!((Boolean)ret.value));
    	return ret;
    }

    public MetaElement lt(MetaElement a, MetaElement b) throws CalcException {
        switch (a.getValueType()) {
        case VariantType.vtBool:
            switch (b.getValueType()) {
            case VariantType.vtBool:
                return new MetaElement(new Boolean(!(a.getAsBoolean() &&
                        b.getAsBoolean())));
            default:
                throw new CalcException(4015);
            }
        case VariantType.vtInt:
            switch (b.getValueType()) {
            case VariantType.vtBool:
                throw new CalcException(4015);
            case VariantType.vtInt:
                return new MetaElement(new Boolean((b.getAsInt() < a.getAsInt())));
            case VariantType.vtDouble:
                return new MetaElement(new Boolean(b.getAsDouble() - a.getAsDouble() < 0d));
            case VariantType.vtLong:
            	return new MetaElement(new Boolean(b.getAsLong() < a.getAsLong()));
            case VariantType.vtString:
            	return new MetaElement(new Boolean(b.getAsInt() <
            	a.getAsInt()));
            default:
                throw new CalcException(4015);
            }
        case VariantType.vtDouble:
            switch (b.getValueType()) {
            case VariantType.vtInt:
                return new MetaElement(new Boolean(b.getAsDouble() < a.getAsInt()));
            case VariantType.vtDouble:
            case VariantType.vtLong:
                return new MetaElement(new Boolean(b.getAsDouble() <
                        a.getAsDouble()));
            case VariantType.vtString:
            	return new MetaElement(new Boolean(b.getAsDouble() <
            	a.getAsDouble()));
            default:
                throw new CalcException(4015);
            }
        case VariantType.vtString:
            switch (b.getValueType()) {
            case VariantType.vtString:
                return new MetaElement(new Boolean((b.toString().compareTo(a.
                        toString()) < 0)));
            default:
                throw new CalcException(4015);
            }
        case VariantType.vtDateTime:
            switch (b.getValueType()) {
            case VariantType.vtDateTime:
                return new MetaElement(new Boolean(b.getAsDateTime().
                        before(a.getAsDateTime())));
            case VariantType.vtString:
            	return new MetaElement(new Boolean(b.getAsDateTime().before(a.getAsDateTime())));
            default:
                throw new CalcException(4015);
            }
        default:
            throw new CalcException(4015);
        }
    }

    public MetaElement lge(MetaElement a, MetaElement b) {
        try {
            return new MetaElement(new Boolean(b.equals(a) ||
                                               ((Boolean) lg(a, b).value()).
                                               booleanValue()));
        } catch (Exception ex) {
            return new MetaElement(Boolean.FALSE);
        }
    }

    public MetaElement lte(MetaElement b, MetaElement a) {
        try {
            return new MetaElement(new Boolean(b.equals(a) ||
                                               ((Boolean) lt(b, a).value()).
                                               booleanValue()));
        } catch (Exception ex) {
            return new MetaElement(Boolean.FALSE);
        }
    }

    public MetaElement eq(MetaElement a, MetaElement b) {
        return new MetaElement(new Boolean(a.equals(b)));
    }

    public MetaElement or_(MetaElement b, MetaElement a) throws CalcException {
        if (a.getValueType() == VariantType.vtBool &&
            b.getValueType() == VariantType.vtBool) {
            return new MetaElement(new Boolean(a.getAsBoolean() ||
                                               (b.getAsBoolean())));

        } else
            throw new CalcException(4016);
    }

    public MetaElement notEq_(MetaElement a, MetaElement b) throws
            CalcException {
        return new MetaElement(new Boolean(a.equals(b)));
    }

    public MetaElement and_(MetaElement a, MetaElement b) throws CalcException {
        if (a.getValueType() == VariantType.vtBool &&
            b.getValueType() == VariantType.vtBool) {
            return new MetaElement(new Boolean(a.getAsBoolean()
                                               && b.getAsBoolean()));

        } else
            throw (new CalcException(4017));
    }
    public MetaElement embeddedFunction(FunctionDesc fd) throws CalcException {
        String fName = fd.functionName;
        FunctionIntf func = (FunctionIntf)this.funcMap.get(fName);
        if(null != func){
        	Object ret = func.execute(fd);
        	if(null == ret)
        		return new MetaElement("");
        	return new MetaElement(ret);
        }
        if("isEmpty".equalsIgnoreCase(fName)){
        	 if (fd.size() < 1)
        	        throw new CalcException(4018);
        	 Object param = fd.get(0).toString();
        	 return new MetaElement(new Boolean(null == param || "".equals(param))); 
        }else if("decode".equalsIgnoreCase(fName)){
        	if(fd.size() < 3 || fd.size()%2 != 0)
        		throw new CalcException(4019);
        	int sz = fd.size();
        	for(int i = 1; i< sz-1; i+=2){
        		if(fd.get(0).equals(fd.get(i))){
        			return new MetaElement(fd.get(i + 1).toString());
        		}
        	}
        	return new MetaElement(fd.get(sz - 1).toString());
        }
        else if ("PI".equalsIgnoreCase(fName))
          return new MetaElement(new Double(java.lang.Math.PI));
        else if ("NOW".equalsIgnoreCase(fName))
          return new MetaElement(new java.util.Date(System.currentTimeMillis()));
        else if ("SIN".equalsIgnoreCase(fName)) {
          if (fd.size() < 1)
            throw new CalcException(
                4018);
          return new MetaElement(new Double(java.lang.Math.sin(Double.parseDouble(
              fd.get(0).toString()))));
        }
        else if ("COS".equalsIgnoreCase(fName)) {
          if (fd.size() < 1)
            throw new CalcException(
                4018);
          return new MetaElement(new Double(java.lang.Math.cos(Double.parseDouble(  fd.get(0).toString()))));
        }
        else if ("ABS".equalsIgnoreCase(fName)) {
          if (fd.size() < 1)
            throw new CalcException(
                4018);
          return new MetaElement(new Double(java.lang.Math.abs(Double.parseDouble(
              fd.get(0).toString()))));
        }
        else if ("SQRT".equalsIgnoreCase(fName)) {
          if (fd.size() < 1)
            throw new CalcException(
                4018);
          return new MetaElement(new Double(java.lang.Math.sqrt(Double.parseDouble (
               fd.get(0).toString()))));
        }
        else if ("POWER".equalsIgnoreCase(fName)) {
          if (fd.size() != 2)
            throw new CalcException(4020);
          double x = Double.parseDouble(fd.get(0).toString());
          double y = Double.parseDouble(fd.get(1).toString());
          return new MetaElement(new Double(java.lang.Math.pow(x,y)));
        }
        else if ("LENGTH".equalsIgnoreCase(fName)) {
          if (fd.size() != 1)
            throw new CalcException(4020);
          return new MetaElement(new Integer(fd.get(0).toString().length()));
        }
        else if ("UPPER".equalsIgnoreCase(fName)) {
          if (fd.size() != 1)
            throw new CalcException(4020);
          return new MetaElement(fd.get(0).toString().toUpperCase());
        }
        else if ("LOWER".equalsIgnoreCase(fName)) {
          if (fd.size() != 1)
            throw new CalcException(
                4021);
          return new MetaElement(fd.get(0).toString().toLowerCase());
        }
        else if ("print".equalsIgnoreCase(fName)) {
        	String result = null;
        	for(int i =0;i<fd.size();i++){
        	  result = fd.get(i).toString();
	          System.out.println(result);
        	}
        	return new MetaElement(result);
        }
        else if ("SUBSTRING".equalsIgnoreCase(fName)) {
          if (fd.size() != 3)
            throw new CalcException(
                4022);
          int fromIndex = Integer.parseInt(fd.get(1).toString());
          int endIndex = Integer.parseInt(fd.get(2).toString());
          String ret = "";
          if (fromIndex > fd.get(0).toString().length() - 1
              || endIndex > fd.get(0).toString().length() - 1)
            ;
          else {
            if (fromIndex > endIndex)
              fromIndex = endIndex;
            if (fromIndex < 0)
              fromIndex = 0;
            if (endIndex < 0)
              endIndex = fd.get(0).toString().length() - 1;
          }
          ret = fd.get(0).toString().substring(fromIndex, fromIndex + endIndex);
          return new MetaElement(ret);
        }else if("floor".equalsIgnoreCase(fName)){
        	if (fd.size() < 1)
                throw new CalcException(4023);
            double x = Double.parseDouble(fd.get(0).toString());
            return new MetaElement(new Double(Math.floor(x)).intValue());
        }else if("round".equalsIgnoreCase(fName)){
        	if (fd.size() < 1)
                throw new CalcException(4023);
            double x = Double.parseDouble(fd.get(0).toString());
            return new MetaElement(Math.round(x));
        }else
          return new MetaElement("");
    }
    public void registerFunction(String key, FunctionIntf func){
    	this.funcMap.put(key, func);
    }
    public void unregisterFunction(String key, FunctionIntf func){
    	this.funcMap.remove(key);
    }
    public void registerCommander(final Object commander){
    	Method[] methods = commander.getClass().getMethods();
    	if(null != methods && methods.length > 0){
    		for(final Method method : methods){
    			FunctionIntf func = new FunctionIntf() {
					@Override
					public Object execute(List<MetaElement> parameters) {
						List<Object> argsList = new ArrayList<Object>();
						if(null != parameters){
							for(MetaElement me : parameters)
								argsList.add(me.value());
						}
						Object ret = null;
						try {
							ret = method.invoke(commander, argsList.toArray());
						} catch (IllegalArgumentException e) {
							e.printStackTrace();
						} catch (IllegalAccessException e) {
							e.printStackTrace();
						} catch (InvocationTargetException e) {
							e.printStackTrace();
						}
						return ret;
					}
				};
				registerFunction(method.getName(),func);
    		}
    	}
    }
	@Override
	public Map getAll() throws VariantIOException {
		HashMap ret = new HashMap();
		if(null != this.dataSource)
			ret.putAll(this.dataSource.getAll());
		ret.putAll(this.varMap);
		return ret;
	}
}
